/******************************************************************************* * Copyright 2019 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ /** * Element.matches() * https://developer.mozilla.org/enUS/docs/Web/API/Element/matches#Polyfill */ if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; } // eslint-disable-next-line valid-jsdoc /** * Element.closest() * https://developer.mozilla.org/enUS/docs/Web/API/Element/closest#Polyfill */ if (!Element.prototype.closest) { Element.prototype.closest = function(s) { "use strict"; var el = this; if (!document.documentElement.contains(el)) { return null; } do { if (el.matches(s)) { return el; } el = el.parentElement || el.parentNode; } while (el !== null && el.nodeType === 1); return null; }; } /******************************************************************************* * Copyright 2019 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ (function() { "use strict"; var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; if (!containerUtils) { // eslint-disable-next-line no-console console.warn("Accordion: container utilities at window.CQ.CoreComponents.container.utils are not available. This can lead to missing features. Ensure the core.wcm.components.commons.site.container client library is included on the page."); } var dataLayerEnabled; var dataLayer; var delay = 100; var NS = "cmp"; var IS = "accordion"; var keyCodes = { ENTER: 13, SPACE: 32, END: 35, HOME: 36, ARROW_LEFT: 37, ARROW_UP: 38, ARROW_RIGHT: 39, ARROW_DOWN: 40 }; var selectors = { self: "[data-" + NS + '-is="' + IS + '"]' }; var cssClasses = { button: { disabled: "cmp-accordion__button--disabled", expanded: "cmp-accordion__button--expanded" }, panel: { hidden: "cmp-accordion__panel--hidden", expanded: "cmp-accordion__panel--expanded" } }; var dataAttributes = { item: { expanded: "data-cmp-expanded" } }; var properties = { /** * Determines whether a single accordion item is forced to be expanded at a time. * Expanding one item will collapse all others. * * @memberof Accordion * @type {Boolean} * @default false */ "singleExpansion": { "default": false, "transform": function(value) { return !(value === null || typeof value === "undefined"); } } }; /** * Accordion Configuration. * * @typedef {Object} AccordionConfig Represents an Accordion configuration * @property {HTMLElement} element The HTMLElement representing the Accordion * @property {Object} options The Accordion options */ /** * Accordion. * * @class Accordion * @classdesc An interactive Accordion component for toggling panels of related content * @param {AccordionConfig} config The Accordion configuration */ function Accordion(config) { var that = this; if (config && config.element) { init(config); } /** * Initializes the Accordion. * * @private * @param {AccordionConfig} config The Accordion configuration */ function init(config) { that._config = config; // prevents multiple initialization config.element.removeAttribute("data-" + NS + "-is"); setupProperties(config.options); cacheElements(config.element); if (that._elements["item"]) { // ensures multiple element types are arrays. that._elements["item"] = Array.isArray(that._elements["item"]) ? that._elements["item"] : [that._elements["item"]]; that._elements["button"] = Array.isArray(that._elements["button"]) ? that._elements["button"] : [that._elements["button"]]; that._elements["panel"] = Array.isArray(that._elements["panel"]) ? that._elements["panel"] : [that._elements["panel"]]; // Expand the item based on deep-link-id if it matches with any existing accordion item id if (containerUtils) { var deepLinkItem = containerUtils.getDeepLinkItem(that, "item"); if (deepLinkItem && !deepLinkItem.hasAttribute(dataAttributes.item.expanded)) { setItemExpanded(deepLinkItem, true); } } if (that._properties.singleExpansion) { // No deep linking if (!deepLinkItem) { var expandedItems = getExpandedItems(); // no expanded item annotated, force the first item to display. if (expandedItems.length === 0) { toggle(0); } // multiple expanded items annotated, display the last item open. if (expandedItems.length > 1) { toggle(expandedItems.length - 1); } } else { // Deep link case // Collapse the items other than which is deep linked for (var j = 0; j < that._elements["item"].length; j++) { if (that._elements["item"][j].id !== deepLinkItem.id && that._elements["item"][j].hasAttribute(dataAttributes.item.expanded)) { setItemExpanded(that._elements["item"][j], false); } } } } refreshItems(); bindEvents(); if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { /* * Editor message handling: * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame * - check that the message data panel container type is correct and that the id (path) matches this specific Accordion component * - if so, route the "navigate" operation to enact a navigation of the Accordion based on index data */ window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { if (message.data && message.data.type === "cmp-accordion" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { if (message.data.operation === "navigate") { // switch to single expansion mode when navigating in edit mode. var singleExpansion = that._properties.singleExpansion; that._properties.singleExpansion = true; toggle(message.data.index); // revert to the configured state. that._properties.singleExpansion = singleExpansion; } } }); } } } /** * Caches the Accordion elements as defined via the {@code data-accordion-hook="ELEMENT_NAME"} markup API. * * @private * @param {HTMLElement} wrapper The Accordion wrapper element */ function cacheElements(wrapper) { that._elements = {}; that._elements.self = wrapper; var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); for (var i = 0; i < hooks.length; i++) { var hook = hooks[i]; if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own accordion elements var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var key = hook.dataset[NS + "Hook" + capitalized]; if (that._elements[key]) { if (!Array.isArray(that._elements[key])) { var tmp = that._elements[key]; that._elements[key] = [tmp]; } that._elements[key].push(hook); } else { that._elements[key] = hook; } } } } /** * Sets up properties for the Accordion based on the passed options. * * @private * @param {Object} options The Accordion options */ function setupProperties(options) { that._properties = {}; for (var key in properties) { if (Object.prototype.hasOwnProperty.call(properties, key)) { var property = properties[key]; var value = null; if (options && options[key] != null) { value = options[key]; // transform the provided option if (property && typeof property.transform === "function") { value = property.transform(value); } } if (value === null) { // value still null, take the property default value = properties[key]["default"]; } that._properties[key] = value; } } } /** * Binds Accordion event handling. * * @private */ function bindEvents() { var buttons = that._elements["button"]; if (buttons) { for (var i = 0; i < buttons.length; i++) { (function(index) { buttons[i].addEventListener("click", function(event) { toggle(index); focusButton(index); }); buttons[i].addEventListener("keydown", function(event) { onButtonKeyDown(event, index); }); })(i); } } } /** * Handles button keydown events. * * @private * @param {Object} event The keydown event * @param {Number} index The index of the button triggering the event */ function onButtonKeyDown(event, index) { var lastIndex = that._elements["button"].length - 1; switch (event.keyCode) { case keyCodes.ARROW_LEFT: case keyCodes.ARROW_UP: event.preventDefault(); if (index > 0) { focusButton(index - 1); } break; case keyCodes.ARROW_RIGHT: case keyCodes.ARROW_DOWN: event.preventDefault(); if (index < lastIndex) { focusButton(index + 1); } break; case keyCodes.HOME: event.preventDefault(); focusButton(0); break; case keyCodes.END: event.preventDefault(); focusButton(lastIndex); break; case keyCodes.ENTER: case keyCodes.SPACE: event.preventDefault(); toggle(index); focusButton(index); break; default: return; } } /** * General handler for toggle of an item. * * @private * @param {Number} index The index of the item to toggle */ function toggle(index) { var item = that._elements["item"][index]; if (item) { if (that._properties.singleExpansion) { // ensure only a single item is expanded if single expansion is enabled. for (var i = 0; i < that._elements["item"].length; i++) { if (that._elements["item"][i] !== item) { var expanded = getItemExpanded(that._elements["item"][i]); if (expanded) { setItemExpanded(that._elements["item"][i], false); } } } } setItemExpanded(item, !getItemExpanded(item)); if (dataLayerEnabled) { var accordionId = that._elements.self.id; var expandedItems = getExpandedItems() .map(function(item) { return getDataLayerId(item); }); var uploadPayload = { component: {} }; uploadPayload.component[accordionId] = { shownItems: expandedItems }; var removePayload = { component: {} }; removePayload.component[accordionId] = { shownItems: undefined }; dataLayer.push(removePayload); dataLayer.push(uploadPayload); } } } /** * Sets an item's expanded state based on the provided flag and refreshes its internals. * * @private * @param {HTMLElement} item The item to mark as expanded, or not expanded * @param {Boolean} expanded true to mark the item expanded, false otherwise */ function setItemExpanded(item, expanded) { if (expanded) { item.setAttribute(dataAttributes.item.expanded, ""); if (dataLayerEnabled) { dataLayer.push({ event: "cmp:show", eventInfo: { path: "component." + getDataLayerId(item) } }); } } else { item.removeAttribute(dataAttributes.item.expanded); if (dataLayerEnabled) { dataLayer.push({ event: "cmp:hide", eventInfo: { path: "component." + getDataLayerId(item) } }); } } refreshItem(item); } /** * Gets an item's expanded state. * * @private * @param {HTMLElement} item The item for checking its expanded state * @returns {Boolean} true if the item is expanded, false otherwise */ function getItemExpanded(item) { return item && item.dataset && item.dataset["cmpExpanded"] !== undefined; } /** * Refreshes an item based on its expanded state. * * @private * @param {HTMLElement} item The item to refresh */ function refreshItem(item) { var expanded = getItemExpanded(item); if (expanded) { expandItem(item); } else { collapseItem(item); } } /** * Refreshes all items based on their expanded state. * * @private */ function refreshItems() { for (var i = 0; i < that._elements["item"].length; i++) { refreshItem(that._elements["item"][i]); } } /** * Returns all expanded items. * * @private * @returns {HTMLElement[]} The expanded items */ function getExpandedItems() { var expandedItems = []; for (var i = 0; i < that._elements["item"].length; i++) { var item = that._elements["item"][i]; var expanded = getItemExpanded(item); if (expanded) { expandedItems.push(item); } } return expandedItems; } /** * Annotates the item and its internals with * the necessary style and accessibility attributes to indicate it is expanded. * * @private * @param {HTMLElement} item The item to annotate as expanded */ function expandItem(item) { var index = that._elements["item"].indexOf(item); if (index > -1) { var button = that._elements["button"][index]; var panel = that._elements["panel"][index]; button.classList.add(cssClasses.button.expanded); // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 setTimeout(function() { button.setAttribute("aria-expanded", true); }, delay); panel.classList.add(cssClasses.panel.expanded); panel.classList.remove(cssClasses.panel.hidden); panel.setAttribute("aria-hidden", false); } } /** * Annotates the item and its internals with * the necessary style and accessibility attributes to indicate it is not expanded. * * @private * @param {HTMLElement} item The item to annotate as not expanded */ function collapseItem(item) { var index = that._elements["item"].indexOf(item); if (index > -1) { var button = that._elements["button"][index]; var panel = that._elements["panel"][index]; button.classList.remove(cssClasses.button.expanded); // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 setTimeout(function() { button.setAttribute("aria-expanded", false); }, delay); panel.classList.add(cssClasses.panel.hidden); panel.classList.remove(cssClasses.panel.expanded); panel.setAttribute("aria-hidden", true); } } /** * Focuses the button at the provided index. * * @private * @param {Number} index The index of the button to focus */ function focusButton(index) { var button = that._elements["button"][index]; button.focus(); } } /** * Scrolls the browser when the URI fragment is changed to the item of the container Accordion component that corresponds to the deep link in the URL fragment, and displays its content. */ function onHashChange() { if (location.hash && location.hash !== "#") { var anchorLocation = decodeURIComponent(location.hash); var anchorElement = document.querySelector(anchorLocation); if (anchorElement && anchorElement.classList.contains("cmp-accordion__item") && !anchorElement.hasAttribute("data-cmp-expanded")) { var anchorElementButton = document.querySelector(anchorLocation + "-button"); if (anchorElementButton) { anchorElementButton.click(); } } } } /** * Reads options data from the Accordion wrapper element, defined via {@code data-cmp-*} data attributes. * * @private * @param {HTMLElement} element The Accordion element to read options data from * @returns {Object} The options read from the component data attributes */ function readData(element) { var data = element.dataset; var options = []; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var reserved = ["is", "hook" + capitalized]; for (var key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { var value = data[key]; if (key.indexOf(NS) === 0) { key = key.slice(NS.length); key = key.charAt(0).toLowerCase() + key.substring(1); if (reserved.indexOf(key) === -1) { options[key] = value; } } } } return options; } /** * Parses the dataLayer string and returns the ID * * @private * @param {HTMLElement} item the accordion item * @returns {String} dataLayerId or undefined */ function getDataLayerId(item) { if (item) { if (item.dataset.cmpDataLayer) { return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; } else { return item.id; } } return null; } /** * Document ready handler and DOM mutation observers. Initializes Accordion components as necessary. * * @private */ function onDocumentReady() { dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); dataLayer = (dataLayerEnabled) ? window.adobeDataLayer = window.adobeDataLayer || [] : undefined; var elements = document.querySelectorAll(selectors.self); for (var i = 0; i < elements.length; i++) { new Accordion({ element: elements[i], options: readData(elements[i]) }); } var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var body = document.querySelector("body"); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // needed for IE var nodesArray = [].slice.call(mutation.addedNodes); if (nodesArray.length > 0) { nodesArray.forEach(function(addedNode) { if (addedNode.querySelectorAll) { var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); elementsArray.forEach(function(element) { new Accordion({ element: element, options: readData(element) }); }); } }); } }); }); observer.observe(body, { subtree: true, childList: true, characterData: true }); } if (document.readyState !== "loading") { onDocumentReady(); } else { document.addEventListener("DOMContentLoaded", onDocumentReady); } if (containerUtils) { window.addEventListener("load", containerUtils.scrollToAnchor, false); } window.addEventListener("hashchange", onHashChange, false); }()); /******************************************************************************* * Copyright 2018 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ /** * Element.matches() * https://developer.mozilla.org/enUS/docs/Web/API/Element/matches#Polyfill */ if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; } // eslint-disable-next-line valid-jsdoc /** * Element.closest() * https://developer.mozilla.org/enUS/docs/Web/API/Element/closest#Polyfill */ if (!Element.prototype.closest) { Element.prototype.closest = function(s) { "use strict"; var el = this; if (!document.documentElement.contains(el)) { return null; } do { if (el.matches(s)) { return el; } el = el.parentElement || el.parentNode; } while (el !== null && el.nodeType === 1); return null; }; } /******************************************************************************* * Copyright 2018 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ /* global CQ */ (function() { "use strict"; var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; if (!containerUtils) { // eslint-disable-next-line no-console console.warn("Tabs: container utilities at window.CQ.CoreComponents.container.utils are not available. This can lead to missing features. Ensure the core.wcm.components.commons.site.container client library is included on the page."); } var dataLayerEnabled; var dataLayer; var NS = "cmp"; var IS = "tabs"; var keyCodes = { END: 35, HOME: 36, ARROW_LEFT: 37, ARROW_UP: 38, ARROW_RIGHT: 39, ARROW_DOWN: 40 }; var selectors = { self: "[data-" + NS + '-is="' + IS + '"]', active: { tab: "cmp-tabs__tab--active", tabpanel: "cmp-tabs__tabpanel--active" } }; /** * Tabs Configuration * * @typedef {Object} TabsConfig Represents a Tabs configuration * @property {HTMLElement} element The HTMLElement representing the Tabs * @property {Object} options The Tabs options */ /** * Tabs * * @class Tabs * @classdesc An interactive Tabs component for navigating a list of tabs * @param {TabsConfig} config The Tabs configuration */ function Tabs(config) { var that = this; if (config && config.element) { init(config); } /** * Initializes the Tabs * * @private * @param {TabsConfig} config The Tabs configuration */ function init(config) { that._config = config; // prevents multiple initialization config.element.removeAttribute("data-" + NS + "-is"); cacheElements(config.element); that._active = getActiveIndex(that._elements["tab"]); if (that._elements.tabpanel) { refreshActive(); bindEvents(); } // Show the tab based on deep-link-id if it matches with any existing tab item id if (containerUtils) { var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "tab"); if (deepLinkItemIdx && deepLinkItemIdx !== -1) { var deepLinkItem = that._elements["tab"][deepLinkItemIdx]; if (deepLinkItem && that._elements["tab"][that._active].id !== deepLinkItem.id) { navigateAndFocusTab(deepLinkItemIdx); } } } if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { /* * Editor message handling: * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data */ CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { if (message.data && message.data.type === "cmp-tabs" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { if (message.data.operation === "navigate") { navigate(message.data.index); } } }); } } /** * Returns the index of the active tab, if no tab is active returns 0 * * @param {Array} tabs Tab elements * @returns {Number} Index of the active tab, 0 if none is active */ function getActiveIndex(tabs) { if (tabs) { for (var i = 0; i < tabs.length; i++) { if (tabs[i].classList.contains(selectors.active.tab)) { return i; } } } return 0; } /** * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API * * @private * @param {HTMLElement} wrapper The Tabs wrapper element */ function cacheElements(wrapper) { that._elements = {}; that._elements.self = wrapper; var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); for (var i = 0; i < hooks.length; i++) { var hook = hooks[i]; if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own tab elements var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var key = hook.dataset[NS + "Hook" + capitalized]; if (that._elements[key]) { if (!Array.isArray(that._elements[key])) { var tmp = that._elements[key]; that._elements[key] = [tmp]; } that._elements[key].push(hook); } else { that._elements[key] = hook; } } } } /** * Binds Tabs event handling * * @private */ function bindEvents() { var tabs = that._elements["tab"]; if (tabs) { for (var i = 0; i < tabs.length; i++) { (function(index) { tabs[i].addEventListener("click", function(event) { navigateAndFocusTab(index); }); tabs[i].addEventListener("keydown", function(event) { onKeyDown(event); }); })(i); } } } /** * Handles tab keydown events * * @private * @param {Object} event The keydown event */ function onKeyDown(event) { var index = that._active; var lastIndex = that._elements["tab"].length - 1; switch (event.keyCode) { case keyCodes.ARROW_LEFT: case keyCodes.ARROW_UP: event.preventDefault(); if (index > 0) { navigateAndFocusTab(index - 1); } break; case keyCodes.ARROW_RIGHT: case keyCodes.ARROW_DOWN: event.preventDefault(); if (index < lastIndex) { navigateAndFocusTab(index + 1); } break; case keyCodes.HOME: event.preventDefault(); navigateAndFocusTab(0); break; case keyCodes.END: event.preventDefault(); navigateAndFocusTab(lastIndex); break; default: return; } } /** * Refreshes the tab markup based on the current {@code Tabs#_active} index * * @private */ function refreshActive() { var tabpanels = that._elements["tabpanel"]; var tabs = that._elements["tab"]; if (tabpanels) { if (Array.isArray(tabpanels)) { for (var i = 0; i < tabpanels.length; i++) { if (i === parseInt(that._active)) { tabpanels[i].classList.add(selectors.active.tabpanel); tabpanels[i].removeAttribute("aria-hidden"); tabs[i].classList.add(selectors.active.tab); tabs[i].setAttribute("aria-selected", true); tabs[i].setAttribute("tabindex", "0"); } else { tabpanels[i].classList.remove(selectors.active.tabpanel); tabpanels[i].setAttribute("aria-hidden", true); tabs[i].classList.remove(selectors.active.tab); tabs[i].setAttribute("aria-selected", false); tabs[i].setAttribute("tabindex", "-1"); } } } else { // only one tab tabpanels.classList.add(selectors.active.tabpanel); tabs.classList.add(selectors.active.tab); } } } /** * Focuses the element and prevents scrolling the element into view * * @param {HTMLElement} element Element to focus */ function focusWithoutScroll(element) { var x = window.scrollX || window.pageXOffset; var y = window.scrollY || window.pageYOffset; element.focus(); window.scrollTo(x, y); } /** * Navigates to the tab at the provided index * * @private * @param {Number} index The index of the tab to navigate to */ function navigate(index) { that._active = index; refreshActive(); } /** * Navigates to the item at the provided index and ensures the active tab gains focus * * @private * @param {Number} index The index of the item to navigate to */ function navigateAndFocusTab(index) { var exActive = that._active; navigate(index); focusWithoutScroll(that._elements["tab"][index]); if (dataLayerEnabled) { var activeItem = getDataLayerId(that._elements.tabpanel[index]); var exActiveItem = getDataLayerId(that._elements.tabpanel[exActive]); dataLayer.push({ event: "cmp:show", eventInfo: { path: "component." + activeItem } }); dataLayer.push({ event: "cmp:hide", eventInfo: { path: "component." + exActiveItem } }); var tabsId = that._elements.self.id; var uploadPayload = { component: {} }; uploadPayload.component[tabsId] = { shownItems: [activeItem] }; var removePayload = { component: {} }; removePayload.component[tabsId] = { shownItems: undefined }; dataLayer.push(removePayload); dataLayer.push(uploadPayload); } } } /** * Scrolls the browser when the URI fragment is changed to the item of the container Tab component that corresponds to the deep link in the URI fragment, and displays its content. */ function onHashChange() { if (location.hash && location.hash !== "#") { var anchorLocation = decodeURIComponent(location.hash); var anchorElement = document.querySelector(anchorLocation); if (anchorElement && anchorElement.classList.contains("cmp-tabs__tab") && !anchorElement.classList.contains("cmp-tabs__tab--active")) { anchorElement.click(); } } } /** * Reads options data from the Tabs wrapper element, defined via {@code data-cmp-*} data attributes * * @private * @param {HTMLElement} element The Tabs element to read options data from * @returns {Object} The options read from the component data attributes */ function readData(element) { var data = element.dataset; var options = []; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var reserved = ["is", "hook" + capitalized]; for (var key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { var value = data[key]; if (key.indexOf(NS) === 0) { key = key.slice(NS.length); key = key.charAt(0).toLowerCase() + key.substring(1); if (reserved.indexOf(key) === -1) { options[key] = value; } } } } return options; } /** * Parses the dataLayer string and returns the ID * * @private * @param {HTMLElement} item the accordion item * @returns {String} dataLayerId or undefined */ function getDataLayerId(item) { if (item) { if (item.dataset.cmpDataLayer) { return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; } else { return item.id; } } return null; } /** * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. * * @private */ function onDocumentReady() { dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); dataLayer = (dataLayerEnabled) ? window.adobeDataLayer = window.adobeDataLayer || [] : undefined; var elements = document.querySelectorAll(selectors.self); for (var i = 0; i < elements.length; i++) { new Tabs({ element: elements[i], options: readData(elements[i]) }); } var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var body = document.querySelector("body"); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // needed for IE var nodesArray = [].slice.call(mutation.addedNodes); if (nodesArray.length > 0) { nodesArray.forEach(function(addedNode) { if (addedNode.querySelectorAll) { var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); elementsArray.forEach(function(element) { new Tabs({ element: element, options: readData(element) }); }); } }); } }); }); observer.observe(body, { subtree: true, childList: true, characterData: true }); } if (document.readyState !== "loading") { onDocumentReady(); } else { document.addEventListener("DOMContentLoaded", onDocumentReady); } if (containerUtils) { window.addEventListener("load", containerUtils.scrollToAnchor, false); } window.addEventListener("hashchange", onHashChange, false); }()); /******************************************************************************* * Copyright 2018 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ (function() { "use strict"; var dataLayerEnabled; var dataLayer; var NS = "cmp"; var IS = "carousel"; var keyCodes = { SPACE: 32, END: 35, HOME: 36, ARROW_LEFT: 37, ARROW_UP: 38, ARROW_RIGHT: 39, ARROW_DOWN: 40 }; var selectors = { self: "[data-" + NS + '-is="' + IS + '"]' }; var properties = { /** * Determines whether the Carousel will automatically transition between slides * * @memberof Carousel * @type {Boolean} * @default false */ "autoplay": { "default": false, "transform": function(value) { return !(value === null || typeof value === "undefined"); } }, /** * Duration (in milliseconds) before automatically transitioning to the next slide * * @memberof Carousel * @type {Number} * @default 5000 */ "delay": { "default": 5000, "transform": function(value) { value = parseFloat(value); return !isNaN(value) ? value : null; } }, /** * Determines whether automatic pause on hovering the carousel is disabled * * @memberof Carousel * @type {Boolean} * @default false */ "autopauseDisabled": { "default": false, "transform": function(value) { return !(value === null || typeof value === "undefined"); } } }; /** * Carousel Configuration * * @typedef {Object} CarouselConfig Represents a Carousel configuration * @property {HTMLElement} element The HTMLElement representing the Carousel * @property {Object} options The Carousel options */ /** * Carousel * * @class Carousel * @classdesc An interactive Carousel component for navigating a list of generic items * @param {CarouselConfig} config The Carousel configuration */ function Carousel(config) { var that = this; if (config && config.element) { init(config); } /** * Initializes the Carousel * * @private * @param {CarouselConfig} config The Carousel configuration */ function init(config) { // prevents multiple initialization config.element.removeAttribute("data-" + NS + "-is"); setupProperties(config.options); cacheElements(config.element); that._active = 0; that._paused = false; if (that._elements.item) { refreshActive(); bindEvents(); resetAutoplayInterval(); refreshPlayPauseActions(); } // TODO: This section is only relevant in edit mode and should move to the editor clientLib if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { /* * Editor message handling: * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame * - check that the message data panel container type is correct and that the id (path) matches this specific Carousel component * - if so, route the "navigate" operation to enact a navigation of the Carousel based on index data */ window.CQ = window.CQ || {}; window.CQ.CoreComponents = window.CQ.CoreComponents || {}; window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { if (message.data && message.data.type === "cmp-carousel" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { if (message.data.operation === "navigate") { navigate(message.data.index); } } }); } } /** * Caches the Carousel elements as defined via the {@code data-carousel-hook="ELEMENT_NAME"} markup API * * @private * @param {HTMLElement} wrapper The Carousel wrapper element */ function cacheElements(wrapper) { that._elements = {}; that._elements.self = wrapper; var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); for (var i = 0; i < hooks.length; i++) { var hook = hooks[i]; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var key = hook.dataset[NS + "Hook" + capitalized]; if (that._elements[key]) { if (!Array.isArray(that._elements[key])) { var tmp = that._elements[key]; that._elements[key] = [tmp]; } that._elements[key].push(hook); } else { that._elements[key] = hook; } } } /** * Sets up properties for the Carousel based on the passed options. * * @private * @param {Object} options The Carousel options */ function setupProperties(options) { that._properties = {}; for (var key in properties) { if (Object.prototype.hasOwnProperty.call(properties, key)) { var property = properties[key]; var value = null; if (options && options[key] != null) { value = options[key]; // transform the provided option if (property && typeof property.transform === "function") { value = property.transform(value); } } if (value === null) { // value still null, take the property default value = properties[key]["default"]; } that._properties[key] = value; } } } /** * Binds Carousel event handling * * @private */ function bindEvents() { if (that._elements["previous"]) { that._elements["previous"].addEventListener("click", function() { var index = getPreviousIndex(); navigate(index); if (dataLayerEnabled) { dataLayer.push({ event: "cmp:show", eventInfo: { path: "component." + getDataLayerId(that._elements.item[index]) } }); } }); } if (that._elements["next"]) { that._elements["next"].addEventListener("click", function() { var index = getNextIndex(); navigate(index); if (dataLayerEnabled) { dataLayer.push({ event: "cmp:show", eventInfo: { path: "component." + getDataLayerId(that._elements.item[index]) } }); } }); } var indicators = that._elements["indicator"]; if (indicators) { for (var i = 0; i < indicators.length; i++) { (function(index) { indicators[i].addEventListener("click", function(event) { navigateAndFocusIndicator(index); }); })(i); } } if (that._elements["pause"]) { if (that._properties.autoplay) { that._elements["pause"].addEventListener("click", onPauseClick); } } if (that._elements["play"]) { if (that._properties.autoplay) { that._elements["play"].addEventListener("click", onPlayClick); } } that._elements.self.addEventListener("keydown", onKeyDown); if (!that._properties.autopauseDisabled) { that._elements.self.addEventListener("mouseenter", onMouseEnter); that._elements.self.addEventListener("mouseleave", onMouseLeave); } // for accessibility we pause animation when a element get focused var items = that._elements["item"]; if (items) { for (var j = 0; j < items.length; j++) { items[j].addEventListener("focusin", onMouseEnter); items[j].addEventListener("focusout", onMouseLeave); } } } /** * Handles carousel keydown events * * @private * @param {Object} event The keydown event */ function onKeyDown(event) { var index = that._active; var lastIndex = that._elements["indicator"].length - 1; switch (event.keyCode) { case keyCodes.ARROW_LEFT: case keyCodes.ARROW_UP: event.preventDefault(); if (index > 0) { navigateAndFocusIndicator(index - 1); } break; case keyCodes.ARROW_RIGHT: case keyCodes.ARROW_DOWN: event.preventDefault(); if (index < lastIndex) { navigateAndFocusIndicator(index + 1); } break; case keyCodes.HOME: event.preventDefault(); navigateAndFocusIndicator(0); break; case keyCodes.END: event.preventDefault(); navigateAndFocusIndicator(lastIndex); break; case keyCodes.SPACE: if (that._properties.autoplay && (event.target !== that._elements["previous"] && event.target !== that._elements["next"])) { event.preventDefault(); if (!that._paused) { pause(); } else { play(); } } if (event.target === that._elements["pause"]) { that._elements["play"].focus(); } if (event.target === that._elements["play"]) { that._elements["pause"].focus(); } break; default: return; } } /** * Handles carousel mouseenter events * * @private * @param {Object} event The mouseenter event */ function onMouseEnter(event) { clearAutoplayInterval(); } /** * Handles carousel mouseleave events * * @private * @param {Object} event The mouseleave event */ function onMouseLeave(event) { resetAutoplayInterval(); } /** * Handles pause element click events * * @private * @param {Object} event The click event */ function onPauseClick(event) { pause(); that._elements["play"].focus(); } /** * Handles play element click events * * @private * @param {Object} event The click event */ function onPlayClick() { play(); that._elements["pause"].focus(); } /** * Pauses the playing of the Carousel. Sets {@code Carousel#_paused} marker. * Only relevant when autoplay is enabled * * @private */ function pause() { that._paused = true; clearAutoplayInterval(); refreshPlayPauseActions(); } /** * Enables the playing of the Carousel. Sets {@code Carousel#_paused} marker. * Only relevant when autoplay is enabled * * @private */ function play() { that._paused = false; // If the Carousel is hovered, don't begin auto transitioning until the next mouse leave event var hovered = false; if (that._elements.self.parentElement) { hovered = that._elements.self.parentElement.querySelector(":hover") === that._elements.self; } if (that._properties.autopauseDisabled || !hovered) { resetAutoplayInterval(); } refreshPlayPauseActions(); } /** * Refreshes the play/pause action markup based on the {@code Carousel#_paused} state * * @private */ function refreshPlayPauseActions() { setActionDisabled(that._elements["pause"], that._paused); setActionDisabled(that._elements["play"], !that._paused); } /** * Refreshes the item markup based on the current {@code Carousel#_active} index * * @private */ function refreshActive() { var items = that._elements["item"]; var indicators = that._elements["indicator"]; if (items) { if (Array.isArray(items)) { for (var i = 0; i < items.length; i++) { if (i === parseInt(that._active)) { items[i].classList.add("cmp-carousel__item--active"); items[i].removeAttribute("aria-hidden"); indicators[i].classList.add("cmp-carousel__indicator--active"); indicators[i].setAttribute("aria-selected", true); indicators[i].setAttribute("tabindex", "0"); } else { items[i].classList.remove("cmp-carousel__item--active"); items[i].setAttribute("aria-hidden", true); indicators[i].classList.remove("cmp-carousel__indicator--active"); indicators[i].setAttribute("aria-selected", false); indicators[i].setAttribute("tabindex", "-1"); } } } else { // only one item items.classList.add("cmp-carousel__item--active"); indicators.classList.add("cmp-carousel__indicator--active"); } } } /** * Focuses the element and prevents scrolling the element into view * * @param {HTMLElement} element Element to focus */ function focusWithoutScroll(element) { var x = window.scrollX || window.pageXOffset; var y = window.scrollY || window.pageYOffset; element.focus(); window.scrollTo(x, y); } /** * Retrieves the next active index, with looping * * @private * @returns {Number} Index of the next carousel item */ function getNextIndex() { return that._active === (that._elements["item"].length - 1) ? 0 : that._active + 1; } /** * Retrieves the previous active index, with looping * * @private * @returns {Number} Index of the previous carousel item */ function getPreviousIndex() { return that._active === 0 ? (that._elements["item"].length - 1) : that._active - 1; } /** * Navigates to the item at the provided index * * @private * @param {Number} index The index of the item to navigate to */ function navigate(index) { if (index < 0 || index > (that._elements["item"].length - 1)) { return; } that._active = index; refreshActive(); if (dataLayerEnabled) { var carouselId = that._elements.self.id; var activeItem = getDataLayerId(that._elements.item[index]); var updatePayload = { component: {} }; updatePayload.component[carouselId] = { shownItems: [activeItem] }; var removePayload = { component: {} }; removePayload.component[carouselId] = { shownItems: undefined }; dataLayer.push(removePayload); dataLayer.push(updatePayload); } // reset the autoplay transition interval following navigation, if not already hovering the carousel if (that._elements.self.parentElement) { if (that._elements.self.parentElement.querySelector(":hover") !== that._elements.self) { resetAutoplayInterval(); } } } /** * Navigates to the item at the provided index and ensures the active indicator gains focus * * @private * @param {Number} index The index of the item to navigate to */ function navigateAndFocusIndicator(index) { navigate(index); focusWithoutScroll(that._elements["indicator"][index]); if (dataLayerEnabled) { dataLayer.push({ event: "cmp:show", eventInfo: { path: "component." + getDataLayerId(that._elements.item[index]) } }); } } /** * Starts/resets automatic slide transition interval * * @private */ function resetAutoplayInterval() { if (that._paused || !that._properties.autoplay) { return; } clearAutoplayInterval(); that._autoplayIntervalId = window.setInterval(function() { if (document.visibilityState && document.hidden) { return; } var indicators = that._elements["indicators"]; if (indicators !== document.activeElement && indicators.contains(document.activeElement)) { // if an indicator has focus, ensure we switch focus following navigation navigateAndFocusIndicator(getNextIndex()); } else { navigate(getNextIndex()); } }, that._properties.delay); } /** * Clears/pauses automatic slide transition interval * * @private */ function clearAutoplayInterval() { window.clearInterval(that._autoplayIntervalId); that._autoplayIntervalId = null; } /** * Sets the disabled state for an action and toggles the appropriate CSS classes * * @private * @param {HTMLElement} action Action to disable * @param {Boolean} [disable] {@code true} to disable, {@code false} to enable */ function setActionDisabled(action, disable) { if (!action) { return; } if (disable !== false) { action.disabled = true; action.classList.add("cmp-carousel__action--disabled"); } else { action.disabled = false; action.classList.remove("cmp-carousel__action--disabled"); } } } /** * Reads options data from the Carousel wrapper element, defined via {@code data-cmp-*} data attributes * * @private * @param {HTMLElement} element The Carousel element to read options data from * @returns {Object} The options read from the component data attributes */ function readData(element) { var data = element.dataset; var options = []; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var reserved = ["is", "hook" + capitalized]; for (var key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { var value = data[key]; if (key.indexOf(NS) === 0) { key = key.slice(NS.length); key = key.charAt(0).toLowerCase() + key.substring(1); if (reserved.indexOf(key) === -1) { options[key] = value; } } } } return options; } /** * Parses the dataLayer string and returns the ID * * @private * @param {HTMLElement} item the accordion item * @returns {String} dataLayerId or undefined */ function getDataLayerId(item) { if (item) { if (item.dataset.cmpDataLayer) { return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; } else { return item.id; } } return null; } /** * Document ready handler and DOM mutation observers. Initializes Carousel components as necessary. * * @private */ function onDocumentReady() { dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); dataLayer = (dataLayerEnabled) ? window.adobeDataLayer = window.adobeDataLayer || [] : undefined; var elements = document.querySelectorAll(selectors.self); for (var i = 0; i < elements.length; i++) { new Carousel({ element: elements[i], options: readData(elements[i]) }); } var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var body = document.querySelector("body"); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // needed for IE var nodesArray = [].slice.call(mutation.addedNodes); if (nodesArray.length > 0) { nodesArray.forEach(function(addedNode) { if (addedNode.querySelectorAll) { var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); elementsArray.forEach(function(element) { new Carousel({ element: element, options: readData(element) }); }); } }); } }); }); observer.observe(body, { subtree: true, childList: true, characterData: true }); } if (document.readyState !== "loading") { onDocumentReady(); } else { document.addEventListener("DOMContentLoaded", onDocumentReady); } }()); /******************************************************************************* * Copyright 2017 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ if (window.Element && !Element.prototype.closest) { // eslint valid-jsdoc: "off" Element.prototype.closest = function(s) { "use strict"; var matches = (this.document || this.ownerDocument).querySelectorAll(s); var el = this; var i; do { i = matches.length; while (--i >= 0 && matches.item(i) !== el) { // continue } } while ((i < 0) && (el = el.parentElement)); return el; }; } if (window.Element && !Element.prototype.matches) { Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector || function(s) { "use strict"; var matches = (this.document || this.ownerDocument).querySelectorAll(s); var i = matches.length; while (--i >= 0 && matches.item(i) !== this) { // continue } return i > -1; }; } if (!Object.assign) { Object.assign = function(target, varArgs) { // .length of function is 2 "use strict"; if (target === null) { throw new TypeError("Cannot convert undefined or null to object"); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource !== null) { for (var nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }; } (function(arr) { "use strict"; arr.forEach(function(item) { if (Object.prototype.hasOwnProperty.call(item, "remove")) { return; } Object.defineProperty(item, "remove", { configurable: true, enumerable: true, writable: true, value: function remove() { this.parentNode.removeChild(this); } }); }); })([Element.prototype, CharacterData.prototype, DocumentType.prototype]); /******************************************************************************* * Copyright 2016 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ (function() { "use strict"; var NS = "cmp"; var IS = "image"; var EMPTY_PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; var LAZY_THRESHOLD_DEFAULT = 0; var SRC_URI_TEMPLATE_WIDTH_VAR = "{.width}"; var SRC_URI_TEMPLATE_DPR_VAR = "{dpr}"; var selectors = { self: "[data-" + NS + '-is="' + IS + '"]', image: '[data-cmp-hook-image="image"]', map: '[data-cmp-hook-image="map"]', area: '[data-cmp-hook-image="area"]' }; var lazyLoader = { "cssClass": "cmp-image__image--is-loading", "style": { "height": 0, "padding-bottom": "" // will be replaced with % ratio } }; var properties = { /** * An array of alternative image widths (in pixels). * Used to replace a {.width} variable in the src property with an optimal width if a URI template is provided. * * @memberof Image * @type {Number[]} * @default [] */ "widths": { "default": [], "transform": function(value) { var widths = []; value.split(",").forEach(function(item) { item = parseFloat(item); if (!isNaN(item)) { widths.push(item); } }); return widths; } }, /** * Indicates whether the image should be rendered lazily. * * @memberof Image * @type {Boolean} * @default false */ "lazy": { "default": false, "transform": function(value) { return !(value === null || typeof value === "undefined"); } }, /** * Indicates image is DynamicMedia image. * * @memberof Image * @type {Boolean} * @default false */ "dmimage": { "default": false, "transform": function(value) { return !(value === null || typeof value === "undefined"); } }, /** * The lazy threshold. * This is the number of pixels, in advance of becoming visible, when an lazy-loading image should begin * to load. * * @memberof Image * @type {Number} * @default 0 */ "lazythreshold": { "default": 0, "transform": function(value) { var val = parseInt(value); if (isNaN(val)) { return LAZY_THRESHOLD_DEFAULT; } return val; } }, /** * The image source. * * Can be a simple image source, or a URI template representation that * can be variable expanded - useful for building an image configuration with an alternative width. * e.g. '/path/image.coreimg{.width}.jpeg/1506620954214.jpeg' * * @memberof Image * @type {String} */ "src": { "transform": function(value) { return decodeURIComponent(value); } } }; var devicePixelRatio = window.devicePixelRatio || 1; function readData(element) { var data = element.dataset; var options = []; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var reserved = ["is", "hook" + capitalized]; for (var key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { var value = data[key]; if (key.indexOf(NS) === 0) { key = key.slice(NS.length); key = key.charAt(0).toLowerCase() + key.substring(1); if (reserved.indexOf(key) === -1) { options[key] = value; } } } } return options; } function Image(config) { var that = this; var smartCrops = {}; function init(config) { // prevents multiple initialization config.element.removeAttribute("data-" + NS + "-is"); setupProperties(config.options); cacheElements(config.element); // check image is DM asset; if true try to make req=set if (config.options.src && Object.prototype.hasOwnProperty.call(config.options, "dmimage") && (config.options["smartcroprendition"] === "SmartCrop:Auto")) { var request = new XMLHttpRequest(); var url = decodeURIComponent(config.options.src).split(SRC_URI_TEMPLATE_WIDTH_VAR)[0] + "?req=set,json"; request.open("GET", url, false); request.onload = function() { if (request.status >= 200 && request.status < 400) { // success status var responseText = request.responseText; var rePayload = new RegExp(/^(?:\/\*jsonp\*\/)?\s*([^()]+)\(([\s\S]+),\s*"[0-9]*"\);?$/gmi); var rePayloadJSON = new RegExp(/^{[\s\S]*}$/gmi); var resPayload = rePayload.exec(responseText); var payload; if (resPayload) { var payloadStr = resPayload[2]; if (rePayloadJSON.test(payloadStr)) { payload = JSON.parse(payloadStr); } } // check "relation" - only in case of smartcrop preset if (payload && payload.set.relation && payload.set.relation.length > 0) { for (var i = 0; i < payload.set.relation.length; i++) { smartCrops[parseInt(payload.set.relation[i].userdata.SmartCropWidth)] = ":" + payload.set.relation[i].userdata.SmartCropDef; } } } else { // error status } }; request.send(); } if (!that._elements.noscript) { return; } that._elements.container = that._elements.link ? that._elements.link : that._elements.self; unwrapNoScript(); if (that._properties.lazy) { addLazyLoader(); } if (that._elements.map) { that._elements.image.addEventListener("load", onLoad); } window.addEventListener("resize", onWindowResize); ["focus", "click", "load", "transitionend", "animationend", "scroll"].forEach(function(name) { document.addEventListener(name, that.update); }); that._elements.image.addEventListener("cmp-image-redraw", that.update); that.update(); } function loadImage() { var hasWidths = (that._properties.widths && that._properties.widths.length > 0) || Object.keys(smartCrops).length > 0; var replacement; if (Object.keys(smartCrops).length > 0) { var optimalWidth = getOptimalWidth(Object.keys(smartCrops), false); replacement = smartCrops[optimalWidth]; } else { replacement = hasWidths ? (that._properties.dmimage ? "" : ".") + getOptimalWidth(that._properties.widths, true) : ""; } var url = that._properties.src.replace(SRC_URI_TEMPLATE_WIDTH_VAR, replacement); url = url.replace(SRC_URI_TEMPLATE_DPR_VAR, devicePixelRatio); var imgSrcAttribute = that._elements.image.getAttribute("src"); if (url !== imgSrcAttribute) { if (imgSrcAttribute === null || imgSrcAttribute === EMPTY_PIXEL) { that._elements.image.setAttribute("src", url); } else { var urlTemplateParts = that._properties.src.split(SRC_URI_TEMPLATE_WIDTH_VAR); // check if image src was dynamically swapped meanwhile (e.g. by Target) var isImageRefSame = imgSrcAttribute.startsWith(urlTemplateParts[0]); if (isImageRefSame && urlTemplateParts.length > 1) { isImageRefSame = imgSrcAttribute.endsWith(urlTemplateParts[urlTemplateParts.length - 1]); } if (isImageRefSame) { that._elements.image.setAttribute("src", url); if (!hasWidths) { window.removeEventListener("scroll", that.update); } } } } if (that._lazyLoaderShowing) { that._elements.image.addEventListener("load", removeLazyLoader); } } function getOptimalWidth(widths, useDevicePixelRatio) { var container = that._elements.self; var containerWidth = container.clientWidth; while (containerWidth === 0 && container.parentNode) { container = container.parentNode; containerWidth = container.clientWidth; } var dpr = useDevicePixelRatio ? devicePixelRatio : 1; var optimalWidth = containerWidth * dpr; var len = widths.length; var key = 0; while ((key < len - 1) && (widths[key] < optimalWidth)) { key++; } return widths[key].toString(); } function addLazyLoader() { var width = that._elements.image.getAttribute("width"); var height = that._elements.image.getAttribute("height"); if (width && height) { var ratio = (height / width) * 100; var styles = lazyLoader.style; styles["padding-bottom"] = ratio + "%"; for (var s in styles) { if (Object.prototype.hasOwnProperty.call(styles, s)) { that._elements.image.style[s] = styles[s]; } } } that._elements.image.setAttribute("src", EMPTY_PIXEL); that._elements.image.classList.add(lazyLoader.cssClass); that._lazyLoaderShowing = true; } function unwrapNoScript() { var markup = decodeNoscript(that._elements.noscript.textContent.trim()); var parser = new DOMParser(); // temporary document avoids requesting the image before removing its src var temporaryDocument = parser.parseFromString(markup, "text/html"); var imageElement = temporaryDocument.querySelector(selectors.image); imageElement.removeAttribute("src"); that._elements.container.insertBefore(imageElement, that._elements.noscript); var mapElement = temporaryDocument.querySelector(selectors.map); if (mapElement) { that._elements.container.insertBefore(mapElement, that._elements.noscript); } that._elements.noscript.parentNode.removeChild(that._elements.noscript); if (that._elements.container.matches(selectors.image)) { that._elements.image = that._elements.container; } else { that._elements.image = that._elements.container.querySelector(selectors.image); } that._elements.map = that._elements.container.querySelector(selectors.map); that._elements.areas = that._elements.container.querySelectorAll(selectors.area); } function removeLazyLoader() { that._elements.image.classList.remove(lazyLoader.cssClass); for (var property in lazyLoader.style) { if (Object.prototype.hasOwnProperty.call(lazyLoader.style, property)) { that._elements.image.style[property] = ""; } } that._elements.image.removeEventListener("load", removeLazyLoader); that._lazyLoaderShowing = false; } function isLazyVisible() { if (that._elements.container.offsetParent === null) { return false; } var wt = window.pageYOffset; var wb = wt + document.documentElement.clientHeight; var et = that._elements.container.getBoundingClientRect().top + wt; var eb = et + that._elements.container.clientHeight; return eb >= wt - that._properties.lazythreshold && et <= wb + that._properties.lazythreshold; } function resizeAreas() { if (that._elements.areas && that._elements.areas.length > 0) { for (var i = 0; i < that._elements.areas.length; i++) { var width = that._elements.image.width; var height = that._elements.image.height; if (width && height) { var relcoords = that._elements.areas[i].dataset.cmpRelcoords; if (relcoords) { var relativeCoordinates = relcoords.split(","); var coordinates = new Array(relativeCoordinates.length); for (var j = 0; j < coordinates.length; j++) { if (j % 2 === 0) { coordinates[j] = parseInt(relativeCoordinates[j] * width); } else { coordinates[j] = parseInt(relativeCoordinates[j] * height); } } that._elements.areas[i].coords = coordinates; } } } } } function cacheElements(wrapper) { that._elements = {}; that._elements.self = wrapper; var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); for (var i = 0; i < hooks.length; i++) { var hook = hooks[i]; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var key = hook.dataset[NS + "Hook" + capitalized]; that._elements[key] = hook; } } function setupProperties(options) { that._properties = {}; for (var key in properties) { if (Object.prototype.hasOwnProperty.call(properties, key)) { var property = properties[key]; if (options && options[key] != null) { if (property && typeof property.transform === "function") { that._properties[key] = property.transform(options[key]); } else { that._properties[key] = options[key]; } } else { that._properties[key] = properties[key]["default"]; } } } } function onWindowResize() { that.update(); resizeAreas(); } function onLoad() { resizeAreas(); } that.update = function() { if (that._properties.lazy) { if (isLazyVisible()) { loadImage(); } } else { loadImage(); } }; if (config && config.element) { init(config); } } function onDocumentReady() { var elements = document.querySelectorAll(selectors.self); for (var i = 0; i < elements.length; i++) { new Image({ element: elements[i], options: readData(elements[i]) }); } var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var body = document.querySelector("body"); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // needed for IE var nodesArray = [].slice.call(mutation.addedNodes); if (nodesArray.length > 0) { nodesArray.forEach(function(addedNode) { if (addedNode.querySelectorAll) { var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); elementsArray.forEach(function(element) { new Image({ element: element, options: readData(element) }); }); } }); } }); }); observer.observe(body, { subtree: true, childList: true, characterData: true }); } if (document.readyState !== "loading") { onDocumentReady(); } else { document.addEventListener("DOMContentLoaded", onDocumentReady); } /* on drag & drop of the component into a parsys, noscript's content will be escaped multiple times by the editor which creates the DOM for editing; the HTML parser cannot be used here due to the multiple escaping */ function decodeNoscript(text) { text = text.replace(/&(amp;)*lt;/g, "<"); text = text.replace(/&(amp;)*gt;/g, ">"); return text; } })(); /******************************************************************************* * Copyright 2017 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ (function() { "use strict"; var NS = "cmp"; var IS = "search"; var DELAY = 300; // time before fetching new results when the user is typing a search string var LOADING_DISPLAY_DELAY = 300; // minimum time during which the loading indicator is displayed var PARAM_RESULTS_OFFSET = "resultsOffset"; var keyCodes = { TAB: 9, ENTER: 13, ESCAPE: 27, ARROW_UP: 38, ARROW_DOWN: 40 }; var selectors = { self: "[data-" + NS + '-is="' + IS + '"]', item: { self: "[data-" + NS + "-hook-" + IS + '="item"]', title: "[data-" + NS + "-hook-" + IS + '="itemTitle"]', focused: "." + NS + "-search__item--is-focused" } }; var properties = { /** * The minimum required length of the search term before results are fetched. * * @memberof Search * @type {Number} * @default 3 */ minLength: { "default": 3, transform: function(value) { value = parseFloat(value); return isNaN(value) ? null : value; } }, /** * The maximal number of results fetched by a search request. * * @memberof Search * @type {Number} * @default 10 */ resultsSize: { "default": 10, transform: function(value) { value = parseFloat(value); return isNaN(value) ? null : value; } } }; var idCount = 0; function readData(element) { var data = element.dataset; var options = []; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var reserved = ["is", "hook" + capitalized]; for (var key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { var value = data[key]; if (key.indexOf(NS) === 0) { key = key.slice(NS.length); key = key.charAt(0).toLowerCase() + key.substring(1); if (reserved.indexOf(key) === -1) { options[key] = value; } } } } return options; } function toggleShow(element, show) { if (element) { if (show !== false) { element.style.display = "block"; element.setAttribute("aria-hidden", false); } else { element.style.display = "none"; element.setAttribute("aria-hidden", true); } } } function serialize(form) { var query = []; if (form && form.elements) { for (var i = 0; i < form.elements.length; i++) { var node = form.elements[i]; if (!node.disabled && node.name) { var param = [node.name, encodeURIComponent(node.value)]; query.push(param.join("=")); } } } return query.join("&"); } function mark(node, regex) { if (!node || !regex) { return; } // text nodes if (node.nodeType === 3) { var nodeValue = node.nodeValue; var match = regex.exec(nodeValue); if (nodeValue && match) { var element = document.createElement("mark"); element.className = NS + "-search__item-mark"; element.appendChild(document.createTextNode(match[0])); var after = node.splitText(match.index); after.nodeValue = after.nodeValue.substring(match[0].length); node.parentNode.insertBefore(element, after); } } else if (node.hasChildNodes()) { for (var i = 0; i < node.childNodes.length; i++) { // recurse mark(node.childNodes[i], regex); } } } function Search(config) { if (config.element) { // prevents multiple initialization config.element.removeAttribute("data-" + NS + "-is"); } this._cacheElements(config.element); this._setupProperties(config.options); this._action = this._elements.form.getAttribute("action"); this._resultsOffset = 0; this._hasMoreResults = true; this._elements.input.addEventListener("input", this._onInput.bind(this)); this._elements.input.addEventListener("focus", this._onInput.bind(this)); this._elements.input.addEventListener("keydown", this._onKeydown.bind(this)); this._elements.clear.addEventListener("click", this._onClearClick.bind(this)); document.addEventListener("click", this._onDocumentClick.bind(this)); this._elements.results.addEventListener("scroll", this._onScroll.bind(this)); this._makeAccessible(); } Search.prototype._displayResults = function() { if (this._elements.input.value.length === 0) { toggleShow(this._elements.clear, false); this._cancelResults(); } else if (this._elements.input.value.length < this._properties.minLength) { toggleShow(this._elements.clear, true); } else { this._updateResults(); toggleShow(this._elements.clear, true); } }; Search.prototype._onScroll = function(event) { // fetch new results when the results to be scrolled down are less than the visible results if (this._elements.results.scrollTop + 2 * this._elements.results.clientHeight >= this._elements.results.scrollHeight) { this._resultsOffset += this._properties.resultsSize; this._displayResults(); } }; Search.prototype._onInput = function(event) { var self = this; self._cancelResults(); // start searching when the search term reaches the minimum length this._timeout = setTimeout(function() { self._displayResults(); }, DELAY); }; Search.prototype._onKeydown = function(event) { var self = this; switch (event.keyCode) { case keyCodes.TAB: if (self._resultsOpen()) { toggleShow(self._elements.results, false); self._elements.input.setAttribute("aria-expanded", "false"); } break; case keyCodes.ENTER: event.preventDefault(); if (self._resultsOpen()) { var focused = self._elements.results.querySelector(selectors.item.focused); if (focused) { focused.click(); } } break; case keyCodes.ESCAPE: self._cancelResults(); break; case keyCodes.ARROW_UP: if (self._resultsOpen()) { event.preventDefault(); self._stepResultFocus(true); } break; case keyCodes.ARROW_DOWN: if (self._resultsOpen()) { event.preventDefault(); self._stepResultFocus(); } else { // test the input and if necessary fetch and display the results self._onInput(); } break; default: return; } }; Search.prototype._onClearClick = function(event) { event.preventDefault(); this._elements.input.value = ""; toggleShow(this._elements.clear, false); toggleShow(this._elements.results, false); this._elements.input.setAttribute("aria-expanded", "false"); }; Search.prototype._onDocumentClick = function(event) { var inputContainsTarget = this._elements.input.contains(event.target); var resultsContainTarget = this._elements.results.contains(event.target); if (!(inputContainsTarget || resultsContainTarget)) { toggleShow(this._elements.results, false); this._elements.input.setAttribute("aria-expanded", "false"); } }; Search.prototype._resultsOpen = function() { return this._elements.results.style.display !== "none"; }; Search.prototype._makeAccessible = function() { var id = NS + "-search-results-" + idCount; this._elements.input.setAttribute("aria-owns", id); this._elements.results.id = id; idCount++; }; Search.prototype._generateItems = function(data, results) { var self = this; data.forEach(function(item) { var el = document.createElement("span"); el.innerHTML = self._elements.itemTemplate.innerHTML; el.querySelectorAll(selectors.item.title)[0].appendChild(document.createTextNode(item.title)); el.querySelectorAll(selectors.item.self)[0].setAttribute("href", item.url); results.innerHTML += el.innerHTML; }); }; Search.prototype._markResults = function() { var nodeList = this._elements.results.querySelectorAll(selectors.item.self); var escapedTerm = this._elements.input.value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); var regex = new RegExp("(" + escapedTerm + ")", "gi"); for (var i = this._resultsOffset - 1; i < nodeList.length; ++i) { var result = nodeList[i]; mark(result, regex); } }; Search.prototype._stepResultFocus = function(reverse) { var results = this._elements.results.querySelectorAll(selectors.item.self); var focused = this._elements.results.querySelector(selectors.item.focused); var newFocused; var index = Array.prototype.indexOf.call(results, focused); var focusedCssClass = NS + "-search__item--is-focused"; if (results.length > 0) { if (!reverse) { // highlight the next result if (index < 0) { results[0].classList.add(focusedCssClass); results[0].setAttribute("aria-selected", "true"); } else if (index + 1 < results.length) { results[index].classList.remove(focusedCssClass); results[index].setAttribute("aria-selected", "false"); results[index + 1].classList.add(focusedCssClass); results[index + 1].setAttribute("aria-selected", "true"); } // if the last visible result is partially hidden, scroll up until it's completely visible newFocused = this._elements.results.querySelector(selectors.item.focused); if (newFocused) { var bottomHiddenHeight = newFocused.offsetTop + newFocused.offsetHeight - this._elements.results.scrollTop - this._elements.results.clientHeight; if (bottomHiddenHeight > 0) { this._elements.results.scrollTop += bottomHiddenHeight; } else { this._onScroll(); } } } else { // highlight the previous result if (index >= 1) { results[index].classList.remove(focusedCssClass); results[index].setAttribute("aria-selected", "false"); results[index - 1].classList.add(focusedCssClass); results[index - 1].setAttribute("aria-selected", "true"); } // if the first visible result is partially hidden, scroll down until it's completely visible newFocused = this._elements.results.querySelector(selectors.item.focused); if (newFocused) { var topHiddenHeight = this._elements.results.scrollTop - newFocused.offsetTop; if (topHiddenHeight > 0) { this._elements.results.scrollTop -= topHiddenHeight; } } } } }; Search.prototype._updateResults = function() { var self = this; if (self._hasMoreResults) { var request = new XMLHttpRequest(); var url = self._action + "?" + serialize(self._elements.form) + "&" + PARAM_RESULTS_OFFSET + "=" + self._resultsOffset; request.open("GET", url, true); request.onload = function() { // when the results are loaded: hide the loading indicator and display the search icon after a minimum period setTimeout(function() { toggleShow(self._elements.loadingIndicator, false); toggleShow(self._elements.icon, true); }, LOADING_DISPLAY_DELAY); if (request.status >= 200 && request.status < 400) { // success status var data = JSON.parse(request.responseText); if (data.length > 0) { self._generateItems(data, self._elements.results); self._markResults(); toggleShow(self._elements.results, true); self._elements.input.setAttribute("aria-expanded", "true"); } else { self._hasMoreResults = false; } // the total number of results is not a multiple of the fetched results: // -> we reached the end of the query if (self._elements.results.querySelectorAll(selectors.item.self).length % self._properties.resultsSize > 0) { self._hasMoreResults = false; } } else { // error status } }; // when the results are loading: display the loading indicator and hide the search icon toggleShow(self._elements.loadingIndicator, true); toggleShow(self._elements.icon, false); request.send(); } }; Search.prototype._cancelResults = function() { clearTimeout(this._timeout); this._elements.results.scrollTop = 0; this._resultsOffset = 0; this._hasMoreResults = true; this._elements.results.innerHTML = ""; this._elements.input.setAttribute("aria-expanded", "false"); }; Search.prototype._cacheElements = function(wrapper) { this._elements = {}; this._elements.self = wrapper; var hooks = this._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); for (var i = 0; i < hooks.length; i++) { var hook = hooks[i]; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var key = hook.dataset[NS + "Hook" + capitalized]; this._elements[key] = hook; } }; Search.prototype._setupProperties = function(options) { this._properties = {}; for (var key in properties) { if (Object.prototype.hasOwnProperty.call(properties, key)) { var property = properties[key]; if (options && options[key] != null) { if (property && typeof property.transform === "function") { this._properties[key] = property.transform(options[key]); } else { this._properties[key] = options[key]; } } else { this._properties[key] = properties[key]["default"]; } } } }; function onDocumentReady() { var elements = document.querySelectorAll(selectors.self); for (var i = 0; i < elements.length; i++) { new Search({ element: elements[i], options: readData(elements[i]) }); } var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var body = document.querySelector("body"); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // needed for IE var nodesArray = [].slice.call(mutation.addedNodes); if (nodesArray.length > 0) { nodesArray.forEach(function(addedNode) { if (addedNode.querySelectorAll) { var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); elementsArray.forEach(function(element) { new Search({ element: element, options: readData(element) }); }); } }); } }); }); observer.observe(body, { subtree: true, childList: true, characterData: true }); } if (document.readyState !== "loading") { onDocumentReady(); } else { document.addEventListener("DOMContentLoaded", onDocumentReady); } })(); /******************************************************************************* * Copyright 2016 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ (function() { "use strict"; var NS = "cmp"; var IS = "formText"; var IS_DASH = "form-text"; var selectors = { self: "[data-" + NS + '-is="' + IS + '"]' }; var properties = { /** * A validation message to display if there is a type mismatch between the user input and expected input. * * @type {String} */ constraintMessage: "", /** * A validation message to display if no input is supplied, but input is expected for the field. * * @type {String} */ requiredMessage: "" }; function readData(element) { var data = element.dataset; var options = []; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var reserved = ["is", "hook" + capitalized]; for (var key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { var value = data[key]; if (key.indexOf(NS) === 0) { key = key.slice(NS.length); key = key.charAt(0).toLowerCase() + key.substring(1); if (reserved.indexOf(key) === -1) { options[key] = value; } } } } return options; } function FormText(config) { if (config.element) { // prevents multiple initialization config.element.removeAttribute("data-" + NS + "-is"); } this._cacheElements(config.element); this._setupProperties(config.options); this._elements.input.addEventListener("invalid", this._onInvalid.bind(this)); this._elements.input.addEventListener("input", this._onInput.bind(this)); } FormText.prototype._onInvalid = function(event) { event.target.setCustomValidity(""); if (event.target.validity.typeMismatch) { if (this._properties.constraintMessage) { event.target.setCustomValidity(this._properties.constraintMessage); } } else if (event.target.validity.valueMissing) { if (this._properties.requiredMessage) { event.target.setCustomValidity(this._properties.requiredMessage); } } }; FormText.prototype._onInput = function(event) { event.target.setCustomValidity(""); }; FormText.prototype._cacheElements = function(wrapper) { this._elements = {}; this._elements.self = wrapper; var hooks = this._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS_DASH + "]"); for (var i = 0; i < hooks.length; i++) { var hook = hooks[i]; var capitalized = IS; capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); var key = hook.dataset[NS + "Hook" + capitalized]; this._elements[key] = hook; } }; FormText.prototype._setupProperties = function(options) { this._properties = {}; for (var key in properties) { if (Object.prototype.hasOwnProperty.call(properties, key)) { var property = properties[key]; if (options && options[key] != null) { if (property && typeof property.transform === "function") { this._properties[key] = property.transform(options[key]); } else { this._properties[key] = options[key]; } } else { this._properties[key] = properties[key]["default"]; } } } }; function onDocumentReady() { var elements = document.querySelectorAll(selectors.self); for (var i = 0; i < elements.length; i++) { new FormText({ element: elements[i], options: readData(elements[i]) }); } var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var body = document.querySelector("body"); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // needed for IE var nodesArray = [].slice.call(mutation.addedNodes); if (nodesArray.length > 0) { nodesArray.forEach(function(addedNode) { if (addedNode.querySelectorAll) { var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); elementsArray.forEach(function(element) { new FormText({ element: element, options: readData(element) }); }); } }); } }); }); observer.observe(body, { subtree: true, childList: true, characterData: true }); } if (document.readyState !== "loading") { onDocumentReady(); } else { document.addEventListener("DOMContentLoaded", onDocumentReady); } })(); /******************************************************************************* * Copyright 2020 Adobe * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ (function() { "use strict"; var NS = "cmp"; var IS = "pdfviewer"; var SDK_URL = "https://documentcloud.adobe.com/view-sdk/main.js"; var SDK_READY_EVENT = "adobe_dc_view_sdk.ready"; var selectors = { self: "[data-" + NS + '-is="' + IS + '"]', sdkScript: 'script[src="' + SDK_URL + '"]' }; function initSDK() { var sdkIncluded = document.querySelectorAll(selectors.sdkScript).length > 0; if (!window.adobe_dc_view_sdk && !sdkIncluded) { var dcv = document.createElement("script"); dcv.type = "text/javascript"; dcv.src = SDK_URL; document.body.appendChild(dcv); } } function previewPdf(component) { // prevents multiple initialization component.removeAttribute("data-" + NS + "-is"); // add the view sdk to the page initSDK(); // manage the preview if (component.dataset && component.id) { if (window.AdobeDC && window.AdobeDC.View) { dcView(component); } else { document.addEventListener(SDK_READY_EVENT, function() { dcView(component); }); } } } function dcView(component) { var adobeDCView = new window.AdobeDC.View({ clientId: component.dataset.cmpClientId, divId: component.id + "-content", reportSuiteId: component.dataset.cmpReportSuiteId }); adobeDCView.previewFile({ content: { location: { url: component.dataset.cmpDocumentPath } }, metaData: { fileName: component.dataset.cmpDocumentFileName } }, JSON.parse(component.dataset.cmpViewerConfigJson)); } /** * Document ready handler and DOM mutation observers. Initializes Accordion components as necessary. * * @private */ function onDocumentReady() { var elements = document.querySelectorAll(selectors.self); for (var i = 0; i < elements.length; i++) { previewPdf(elements[i]); } var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var body = document.querySelector("body"); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // needed for IE var nodesArray = [].slice.call(mutation.addedNodes); if (nodesArray.length > 0) { nodesArray.forEach(function(addedNode) { if (addedNode.querySelectorAll) { var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); elementsArray.forEach(function(element) { previewPdf(element); }); } }); } }); }); observer.observe(body, { subtree: true, childList: true, characterData: true }); } if (document.readyState !== "loading") { onDocumentReady(); } else { document.addEventListener("DOMContentLoaded", onDocumentReady); } }()); const modal = document.getElementById("imageModal"); const closeModalBtn = document.getElementById("closeModal"); const fullscreenBtn = document.getElementById("fullscreenBtn"); const zoomBtn = document.getElementById("zoomBtn"); const shareBtn = document.getElementById("shareBtn"); const modalImg = document.getElementById("modalImage"); const shareDropdown = document.getElementById("shareDropdown"); function openImageModal(imageUrl) { console.log(imageUrl); modal.style.display = "flex"; modalImg.src = imageUrl; document.getElementById("download-image").setAttribute("href", imageUrl); document.getElementById( "share-facebook" ).href = `https://www.facebook.com/sharer/sharer.php?u=${imageUrl}`; document.getElementById( "share-twitter" ).href = `https://twitter.com/intent/tweet?url=${imageUrl}`; document.getElementById( "share-pinterest" ).href = `https://pinterest.com/pin/create/button/?url=${imageUrl}`; } closeModalBtn.onclick = function () { modal.style.display = "none"; }; fullscreenBtn.onclick = function () { if (!document.fullscreenElement) { modal.requestFullscreen(); } else { document.exitFullscreen(); } }; let isZoomed = false; zoomBtn.onclick = function () { const zoomIcon = zoomBtn.querySelector("#zoomBtn i"); if (isZoomed) { modalImg.style.transform = "scale(1)"; zoomIcon.classList.remove("fa-search-minus"); zoomIcon.classList.add("fa-search-plus"); } else { modalImg.style.transform = "scale(1.5)"; zoomIcon.classList.remove("fa-search-plus"); zoomIcon.classList.add("fa-search-minus"); } isZoomed = !isZoomed; }; shareBtn.onclick = function () { if (shareDropdown.style.display === "block") { shareDropdown.style.display = "none"; } else { shareDropdown.style.display = "block"; } }; window.onclick = function (event) { if (event.target == modal) { modal.style.display = "none"; shareDropdown.style.display = "none"; } }; function toggleMenu() { const nav = document.querySelector("nav ul"); const i = document.getElementById("icon"); nav.classList.toggle("active"); if (i.classList.contains("fa-bars")) { i.classList.remove("fa-bars"); i.classList.remove("fa-solid"); i.classList.add("fa-solid", "fa-xmark"); } else { i.classList.remove("fa-xmark"); i.classList.remove("fa-solid"); i.classList.add("fa-solid", "fa-bars"); } } document.addEventListener("DOMContentLoaded", function() { const i = document.getElementById("icon"); i.classList.add("fa-solid", "fa-bars"); });